﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace KylinORM.Data.Parser.Base
{
    /// <summary>
    /// 经过分页的Sql语句
    /// </summary>
    internal struct SqlPagerResult
    {
        /// <summary>
        /// 是否分页成功
        /// </summary>
        internal bool Success { get; set; }
        /// <summary>
        /// 统计查询结果
        /// </summary>
        internal string SqlCount { get; set; }
        /// <summary>
        /// 查询条件+排序
        /// </summary>
        internal string SqlSelectRemoved { get; set; }
        /// <summary>
        /// 查询条件
        /// </summary>
        internal string SqlWhere { get; set; }
        /// <summary>
        /// 排序
        /// </summary>
        internal string SqlOrderBy { get; set; }
    }

    /// <summary>
    /// 通用数据库分页查询
    /// </summary>
    /// <remarks>
    /// /* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's.
    ///  * Copyright © 2011 Topten Software.  All Rights Reserved.
    ///  * 
    ///  * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license
    ///  * 
    ///  * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for 
    ///  * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice 
    ///  * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support
    ///  */
    /// </remarks>
    internal class SqlPager
    {
        private static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?<!,\s+)\bFROM\b", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
        private static Regex rxOrderBy = new Regex(@"\bORDER\s+BY\s+(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?<depth>)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
        private static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled);
        public static SqlPagerResult SplitSqlForPaging(string sql)
        {
            SqlPagerResult returnValue = new SqlPagerResult { Success = false };

            // Extract the columns from "SELECT <whatever> FROM"
            var m = rxColumns.Match(sql);
            if (!m.Success)
                return returnValue;
            returnValue.Success = true;

            // Save column list and replace with COUNT(*)
            Group g = m.Groups[1];
            returnValue.SqlSelectRemoved = sql.Substring(g.Index);

            if (rxDistinct.IsMatch(returnValue.SqlSelectRemoved))
                returnValue.SqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length);
            else
                returnValue.SqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length);


            // Look for an "ORDER BY <whatever>" clause
            m = rxOrderBy.Match(returnValue.SqlCount);
            if (!m.Success)
            {
                returnValue.SqlOrderBy = null;
            }
            else
            {
                g = m.Groups[0];
                returnValue.SqlOrderBy = g.ToString();
                returnValue.SqlCount = returnValue.SqlCount.Substring(0, g.Index) + returnValue.SqlCount.Substring(g.Index + g.Length);
            }

            // Extract the columns from "SELECT <whatever> FROM" without ORDER BY
            returnValue.SqlWhere = rxOrderBy.Replace(returnValue.SqlSelectRemoved, "");
            if (rxDistinct.IsMatch(returnValue.SqlWhere))
            {
                returnValue.SqlWhere = "peta_inner.* FROM (SELECT " + returnValue.SqlWhere + ") peta_inner";
            }

            return returnValue;
        }

    }
}
